package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.resource.build.jaxb.BuildDailyProjectData;
import org.hackystat.devcathlon.configuration.ConfigurationException;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.engine.match.MatchException;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Checks if everyone in the team builds at least once day.
 * 
 * <p>
 * Specification:
 * <ul>
 * <li>Wakeup once a day</li>
 * <li>Award points if everyone builds.</li>
 * <li>Award no points if only one person builds.</li>
 * <li>Deduct points if no one builds.</li>
 * </ul>
 * </p>
 * <p>
 * Incentives:
 * <ul>
 * <li>If everyone builds for consecutive amount of days, then award bonus points.
 * </ul>
 * </p>
 * 
 * @author Robin Raqueno
 * 
 */
public class EveryoneBuild extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;
  /** The unique name for this event. */
  public static final String eventName = "Everyone Build";
  /** The unique description for this event. */
  public static final String description = "Give points for everyone building. ";
  /** The bonus weight key. */
  public static final String bonusWeightKey = "BonusWeight";
  /** The actual bonus weight. */
  private int bonusWeight;
  /** The penalty weight key. */
  public static final String penaltyWeightKey = "PenaltyWeight";
  /** The actual penalty weight. */
  private int penaltyWeight;
  /** The day streak key. */
  public static final String dayStreakKey = "DayStreak";
  /** The actual day streak. */
  private int dayStreak;

  /**
   * Creates a new event for Everyone Build.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public EveryoneBuild() throws EventException {
    super(eventName, description);

    try {
      bonusWeight = Integer.parseInt(configManager.getProperty(eventName, bonusWeightKey));
      penaltyWeight = Integer.parseInt(configManager.getProperty(eventName, penaltyWeightKey));
      dayStreak = Integer.parseInt(configManager.getProperty(eventName, dayStreakKey));
    }
    catch (Exception e) {
      throw new EventException("Problem with configuring properties.", e);
    }
  }

  /**
   * Returns the total number of points for this event.
   * 
   * @param score The event score.
   * @return The total points for this event.
   */
  public int computePoints(EventScore score) {
    return score.getBaseScore() + score.getBonusScore(bonusWeightKey);
  }

  /**
   * Creates an EventScore instance for EveryoneBuilds.
   * 
   * @param match The match of interest.
   * @param team The team of interest.
   * @param timestamp The time at which this event is being run.
   * @return The EventScore.
   * @throws EventException If problems occur while processing this event.
   * @throws MatchException if an error occurs.
   */
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException, MatchException {
    // Create the default score, which awards no points.
    EventScore score = new EventScore(timestamp, match, team);
    Project project = match.getProject(team);
    String projectOwner = project.getOwner();
    String projectName = project.getName();
    DailyProjectDataClient dpdClient = match.getDailyProjectDataClient(team);

    // Counters to help for point calculations.
    int totalBuilds = 0;
    int totalNoBuilds = 0;
    int bonusCounter = 0;

    // Value holders to help with calculations.
    int penaltyRes = 0;
    int buildRes = 0;
    int allBuildRes = 0;

    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting build Data for match %s, team %s", match.getName(), team.getName()));

    // Check for builds in the past days to today.
    for (int i = 0, j = dayStreak; i < dayStreak; i++, j--) {
      try {
        BuildDailyProjectData buildData = dpdClient.getBuild(projectOwner, projectName, Tstamp
            .incrementDays(timestamp, -j));

        // If no one in the team builds
        if (buildData.getMemberData().size() == 0) {
          totalNoBuilds += 1;
          penaltyRes = computeBaseScore(score, 0) * totalNoBuilds;
        }

        // If only one person in the team builds.
        else if (buildData.getMemberData().size() == 1) {
          totalBuilds += 1;
          buildRes = computeBaseScore(score, 1) * totalBuilds;
        }

        // If everyone in the team builds
        else if (buildData.getMemberData().size() == (team.getMembers().size() + 1)) {
          bonusCounter += 1;
          allBuildRes = (computeBaseScore(score, buildData.getMemberData().size()) * bonusCounter);
          computeBonusScore(score, bonusCounter);
        }
      }

      catch (Exception e) {
        throw new EventException(String.format("Error getting Build data for match %s, team %s",
            match.getName(), team.getName()), e);
      }
    }

    // Set the base score.
    int results = allBuildRes + buildRes + penaltyRes;
    score.setBaseScore(results);
    return score;
  }

  /**
   * This Computes the bonus score.
   * 
   * @param score The event score.
   * @param days The number of days to check if its a streak.
   * @return The bonus score.
   */
  private int computeBonusScore(EventScore score, int days) {
    if (days == dayStreak) {
      score.addBonusScore(bonusWeightKey, bonusWeight);
      return bonusWeight;
    }
    return 0;
  }

  /**
   * This computes the base score.
   * 
   * @param score The event score.
   * @param builds The number of builds.
   * @return The base score.
   * @throws ConfigurationException If problems occur while processing this event.
   */
  private int computeBaseScore(EventScore score, int builds) throws ConfigurationException {
    // Only one person has built for the day
    if (builds == 1) {
      return 0;
    }

    // Everyone has built for the day
    if ((builds > 0)) {
      return configManager.getBaseScoreWeight(eventName);
    }

    // No one has built for the day
    else {
      return penaltyWeight;
    }
  }

}
